package com.gomro.galaxy.foundation.bi.engine.analysis;

import cn.gomro.cloud.core.common.utils.DateUtils;
import cn.gomro.cloud.core.common.utils.EncryptUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gomro.galaxy.core.batch.ElasticSearchClzItemReader;
import com.gomro.galaxy.core.batch.ElasticSearchClzItemWriter;
import com.gomro.galaxy.core.commons.elastic.ElasticSearchRest;
import com.gomro.galaxy.foundation.bi.domain.Constants;
import com.gomro.galaxy.foundation.bi.domain.enumerater.QuotationStatusEnum;
import com.gomro.galaxy.foundation.bi.domain.enumerater.VendorType;
import com.gomro.galaxy.foundation.bi.domain.event.InternalVendorQuotationEvent;
import com.gomro.galaxy.foundation.bi.domain.model.*;
import com.gomro.galaxy.foundation.bi.domain.model.analysis.WorkbenchVendorQuotation;
import com.gomro.galaxy.foundation.bi.engine.AnalysisLastTime;
import com.gomro.galaxy.foundation.bi.engine.model.AnalysisCache;
import com.gomro.galaxy.foundation.bi.engine.query.BindingInventedVendorQuery;
import com.gomro.galaxy.foundation.bi.engine.query.OrganizationQuery;
import com.gomro.galaxy.foundation.bi.engine.query.PurchaseGoodsQuery;
import com.gomro.galaxy.foundation.bi.engine.query.QuotationGoodsQuery;
import com.gomro.galaxy.foundation.bi.engine.util.BoolQueryBuilders;
import com.gomro.galaxy.foundation.bi.engine.util.EngineUtils;
import com.gomro.galaxy.foundation.bi.exception.BiException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.batch.core.*;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.bus.BusProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.util.CollectionUtils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

import static com.gomro.galaxy.foundation.bi.domain.model.analysis.WorkbenchVendorQuotation.InternalJobSuffix;
import static com.gomro.galaxy.foundation.bi.engine.util.EngineUtils.MAX_BATCH_NUM;

/**
 * 内部供应商报价导出
 */
@Slf4j
@Configuration
public class InternalVendorQuotationJob {
    private static final String BiAnalysisLastName = Constants.BiDataMartIndexPrefix + AnalysisLastTime.TableName;
    private static final String WorkbenchVendorQuotationLastName = WorkbenchVendorQuotation.TableName + InternalJobSuffix;
    private static final String BiWarehouseOrganizationName = Constants.BiWarehouseIndexPrefix + AcquireOrganization.TableName;
    private static final String BiWarehouseMemberName = Constants.BiWarehouseIndexPrefix + AcquireMember.TableName;
    private static final String BiWarehouseBindingInventedVendorName = Constants.BiWarehouseIndexPrefix + AcquireBindingInventedVendor.TableName;
    private static final String BiWarehouseQuotationGoodsName = Constants.BiWarehouseIndexPrefix + AcquireQuotationGoods.TableName;
    private static final String BiWarehousePurchaseGoodsName = Constants.BiWarehouseIndexPrefix + AcquirePurchaseGoods.TableName;
    private static final String BiWarehouseInquiryGoodsName = Constants.BiWarehouseIndexPrefix + AcquireInquiryGoods.TableName;
    private static final String BiWarehouseInquiryName = Constants.BiWarehouseIndexPrefix + AcquireInquiry.TableName;
    private static final String BiDataMartName = Constants.BiDataMartIndexPrefix + WorkbenchVendorQuotation.TableName;
    private static final String JobName = Constants.JobBeanNamePrefix + WorkbenchVendorQuotation.TableName + InternalJobSuffix;
    private static final String StepName1 = Constants.StepBeanNamePrefix + WorkbenchVendorQuotation.TableName + InternalJobSuffix + 1;
    private static final String StepName2 = Constants.StepBeanNamePrefix + WorkbenchVendorQuotation.TableName + InternalJobSuffix + 2;
    private static final String StepName3 = Constants.StepBeanNamePrefix + WorkbenchVendorQuotation.TableName + InternalJobSuffix + 3;
    private static final String StepName4 = Constants.StepBeanNamePrefix + WorkbenchVendorQuotation.TableName + InternalJobSuffix + 4;
    private static final String StepName5 = Constants.StepBeanNamePrefix + WorkbenchVendorQuotation.TableName + InternalJobSuffix + 5;

    private static final String ReaderName1 = Constants.ReaderBeanNamePrefix + WorkbenchVendorQuotation.TableName + InternalJobSuffix + 1;
    private static final String ReaderName2 = Constants.ReaderBeanNamePrefix + WorkbenchVendorQuotation.TableName + InternalJobSuffix + 2;
    private static final String WriterName1 = Constants.WriterBeanNamePrefix + WorkbenchVendorQuotation.TableName + InternalJobSuffix + 1;
    private static final String WriterName2 = Constants.WriterBeanNamePrefix + WorkbenchVendorQuotation.TableName + InternalJobSuffix + 2;
    static final String ProcessName1 = Constants.ProcessBeanNamePrefix + WorkbenchVendorQuotation.TableName + InternalJobSuffix + 1;
    static final String ProcessName2 = Constants.ProcessBeanNamePrefix + WorkbenchVendorQuotation.TableName + InternalJobSuffix + 2;
    static final String ProcessName3 = Constants.ProcessBeanNamePrefix + WorkbenchVendorQuotation.TableName + InternalJobSuffix + 3;
    static final String ProcessName4 = Constants.ProcessBeanNamePrefix + WorkbenchVendorQuotation.TableName + InternalJobSuffix + 4;
    private static final String ProcessName5 = Constants.ProcessBeanNamePrefix + WorkbenchVendorQuotation.TableName + InternalJobSuffix + 5;

    private ApplicationEventPublisher applicationEventPublisher;
    private BusProperties busProperties;
    private JobBuilderFactory jobBuilderFactory;
    private StepBuilderFactory stepBuilderFactory;
    private ElasticSearchRest rest;
    private RestHighLevelClient client;
    private ObjectMapper mapper;
    private AnalysisCache<WorkbenchVendorQuotation> cache = new AnalysisCache<>();

    public InternalVendorQuotationJob(ApplicationEventPublisher applicationEventPublisher, BusProperties busProperties, JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory, RestHighLevelClient client, ObjectMapper mapper) {
        this.applicationEventPublisher = applicationEventPublisher;
        this.busProperties = busProperties;
        this.jobBuilderFactory = jobBuilderFactory;
        this.stepBuilderFactory = stepBuilderFactory;
        this.client = client;
        this.mapper = mapper;
        this.rest = new ElasticSearchRest(client, mapper);
    }

    private JobExecutionListener jobExecutionListener = new JobExecutionListener() {

        @Override
        public void beforeJob(@NonNull JobExecution jobExecution) {

            ElasticSearchRest rest = new ElasticSearchRest(client, mapper);

            // 初始化数据索引
            if (!rest.indexExists(BiDataMartName)) {

                rest.indexCreate(BiDataMartName);
                rest.indexMappingPut(BiDataMartName, "analysis/" + BiDataMartName + ".json", true);
                rest.indexFlush(BiDataMartName, null, null);
            }

            // 初始化最后更新时间索引
            if (!rest.indexExists(BiAnalysisLastName)) {

                rest.indexCreate(BiAnalysisLastName);
                rest.indexMappingPut(BiAnalysisLastName, "analysis/" + BiAnalysisLastName + ".json", true);
                rest.indexFlush(BiAnalysisLastName, null, null);
            }
        }

        @Override
        public void afterJob(@NonNull JobExecution jobExecution) {
            log.warn("[BiEngineAnalysis-InternalVendorQuotationJob] last time ---> {}", DateUtils.dateToStr(cache.getLast()));
            if (Objects.nonNull(cache.getCache())) {
                log.warn("[BiEngineAnalysis-InternalVendorQuotationJob] cache size -> {}", cache.getCache().size());
                cache.getCache().clear();
            }
            // 回写最后更新时间
            if (jobExecution.getStatus() == BatchStatus.COMPLETED) {
                rest.documentCreate(BiAnalysisLastName, WorkbenchVendorQuotationLastName, AnalysisLastTime.builder().id(WorkbenchVendorQuotationLastName).last(cache.getLast()).build());
                applicationEventPublisher.publishEvent(new InternalVendorQuotationEvent(cache.getLast()));
            }
        }
    };

    @Bean(ReaderName1)
    @StepScope
    public ElasticSearchClzItemReader<AcquireQuotationGoods> reader1() {
        List<Long> virtualOrgIds = getVirtualOrgIds();
        // 获取最后更新时间
        AnalysisLastTime analysisLastTime = rest.documentGet(BiAnalysisLastName, WorkbenchVendorQuotationLastName, AnalysisLastTime.class);
        Date start;
        if (analysisLastTime != null && analysisLastTime.getLast() != null) {
            start = analysisLastTime.getLast();
        } else {
            ElasticSearchRest.SearchResultClz<AcquireQuotationGoods> data = rest.searchAsClz(BiWarehouseQuotationGoodsName, ElasticSearchRest.Page.of(1, 1), null, Collections.singleton(SortBuilders.fieldSort("time").order(SortOrder.ASC)), null, AcquireQuotationGoods.class);
            if (Objects.isNull(data) || CollectionUtils.isEmpty(data.getData())) {
                throw new BiException("analysisLastTime init error");
            }
            start = Date.from(data.getData().get(0).getTime().atZone(ZoneId.systemDefault()).toInstant());
        }
        cache.setLast(start);
        Date endDate = EngineUtils.getEndDate(start, (date, date2) -> rest.searchCount(BiWarehouseQuotationGoodsName,
                BoolQueryBuilders.quotationGoods(QuotationGoodsQuery.builder().start(date).end(date2).vendorIds(virtualOrgIds).del(false).build())));
        if (Objects.nonNull(endDate)) {
            cache.setLast(endDate);
        } else {
            endDate = new Date();
            cache.setLast(endDate);
        }

        //初始化缓存, 不能并行任务
        if (Objects.isNull(cache.getCache())) {
            cache.setCache(new HashMap<>());
        }

        BoolQueryBuilder boolQuery = BoolQueryBuilders.quotationGoods(QuotationGoodsQuery.builder().start(start).end(endDate).vendorIds(virtualOrgIds).del(false).build());
        long count = rest.searchCount(BiWarehouseQuotationGoodsName, boolQuery);
        log.warn("[BiEngineAnalysis-InternalVendorQuotationJob] 查询日期 -> {} count -> {}", DateUtils.dateToStr(cache.getLast(), DateUtils.FORMAT_PATTERN_DATE), count);

        ElasticSearchClzItemReader<AcquireQuotationGoods> reader = new ElasticSearchClzItemReader<>(client, mapper, BiWarehouseQuotationGoodsName,
                MAX_BATCH_NUM, boolQuery, Collections.singleton(SortBuilders.fieldSort("id").order(SortOrder.ASC)), AcquireQuotationGoods.class);
        reader.setName(ReaderName1);
        return reader;
    }

    private List<Long> getVirtualOrgIds() {
        OrganizationQuery query = OrganizationQuery.builder().virtual(true).architectureType(1).del(false).build();
        BoolQueryBuilder organizationQuery = BoolQueryBuilders.organization(query);
        long organizationCount = rest.searchCount(BiWarehouseOrganizationName, organizationQuery);
        ElasticSearchRest.SearchResultClz<AcquireOrganization> organizationData = rest.searchAsClz(BiWarehouseOrganizationName,
                ElasticSearchRest.Page.of(1, (int) organizationCount), organizationQuery, null, null, AcquireOrganization.class);
        if (Objects.nonNull(organizationData) && !CollectionUtils.isEmpty(organizationData.getData())) {
            return organizationData.getData().stream().map(AcquireOrganization::getId).collect(Collectors.toList());
        }
        return Collections.singletonList(null);
    }

    /**
     * 获取报价商品信息
     * @return
     */
    @Bean(ProcessName1)
    public ItemProcessor<AcquireQuotationGoods, WorkbenchVendorQuotation> processor1() {
        return acquireQuotationGoods -> {
            LocalDateTime quotationGoodsTime = acquireQuotationGoods.getTime();
            LocalDate localDate = quotationGoodsTime.toLocalDate();
            String id = EncryptUtils.MD5_32(localDate.toString() + acquireQuotationGoods.getId());
            QuotationStatusEnum quotationStatusEnum = QuotationStatusEnum.get(acquireQuotationGoods.getStatus());
            String status = QuotationStatusEnum.QUOTATION_0_NEW.equals(quotationStatusEnum) ? "已参与或已参与未提交" : quotationStatusEnum.des;
            String selected = QuotationStatusEnum.QUOTATION_5_PICKETED.equals(quotationStatusEnum) ? "是" : "否";
            PurchaseGoodsQuery query = PurchaseGoodsQuery.builder().quotationGoodsId(acquireQuotationGoods.getId()).build();
            ElasticSearchRest.SearchResultClz<AcquirePurchaseGoods> resultClz = rest.searchAsClz(BiWarehousePurchaseGoodsName,
                    ElasticSearchRest.Page.of(1, 1), BoolQueryBuilders.purchaseGoods(query), null,
                    null, AcquirePurchaseGoods.class);
            String order;
            if (Objects.nonNull(resultClz) && !CollectionUtils.isEmpty(resultClz.getData())) {
                order = resultClz.getData().get(0).getBought() > 0 ? "是" : "否";
            } else {
                order = "否";
            }
            return WorkbenchVendorQuotation.builder()
                    .id(id)
                    .time(localDate)
                    .vendorId(acquireQuotationGoods.getVendorOrganizationId())
                    .vendorName(StringUtils.trim(acquireQuotationGoods.getVendorOrganizationName()))
                    .manager(acquireQuotationGoods.getMemberFullName())
                    .quotationStatus(status)
                    .quotationGoodsId(acquireQuotationGoods.getId())
                    .quotationTime(acquireQuotationGoods.getTime())
                    .selected(selected)
                    .inquiryGoodsId(acquireQuotationGoods.getInquiryGoodsId())
                    .pretaxPrice(acquireQuotationGoods.getPretaxPrice())
                    .order(order)
                    .customerName("")
                    .inquiryGoodsBrand("")
                    .inquiryGoodsName("")
                    .inquiryGoodsModel("")
                    .inquiryGoodsSpec("")
                    .inquiryGoodsUnit("")
                    .inquiryGoodsNum(0.0)
                    .build();
        };
    }

    @Bean(WriterName1)
    public ItemWriter<WorkbenchVendorQuotation> writer1(){
        return list -> list.forEach(e -> cache.getCache().put(e.getId(), e));
    }

    @Bean(ReaderName2)
    @StepScope
    public ItemReader<WorkbenchVendorQuotation> reader2() {
        return EngineUtils.createCacheReader(cache.getCache());
    }

    /**
     * 获取询价商品信息
     * @return
     */
    @Bean(ProcessName2)
    public ItemProcessor<WorkbenchVendorQuotation, WorkbenchVendorQuotation> processor2() {
        return workbenchVendorQuotation -> {
            Long inquiryGoodsId = workbenchVendorQuotation.getInquiryGoodsId();

            AcquireInquiryGoods acquireInquiryGoods = rest.documentGet(BiWarehouseInquiryGoodsName, inquiryGoodsId.toString(), AcquireInquiryGoods.class);
            if (Objects.isNull(acquireInquiryGoods)) {
                log.warn("[BiEngineAnalysis-InternalVendorQuotationJob] 询价商品"+inquiryGoodsId+"未找到!");
                return workbenchVendorQuotation;
            }
            workbenchVendorQuotation
                    .setInquiryGoodsBrand(acquireInquiryGoods.getBrand())
                    .setInquiryGoodsModel(acquireInquiryGoods.getModel())
                    .setInquiryGoodsName(acquireInquiryGoods.getName())
                    .setInquiryGoodsNum(acquireInquiryGoods.getNum())
                    .setInquiryGoodsUnit(acquireInquiryGoods.getUnit())
                    .setInquiryId(acquireInquiryGoods.getInquiryId())
                    .setInquiryGoodsSpec(acquireInquiryGoods.getSpec());
            return workbenchVendorQuotation;
        };
    }

    /**
     * 获取询价单信息
     * @return
     */
    @Bean(ProcessName3)
    public ItemProcessor<WorkbenchVendorQuotation, WorkbenchVendorQuotation> processor3() {
        return workbenchVendorQuotation -> {
            Long inquiryId = workbenchVendorQuotation.getInquiryId();
            if (Objects.isNull(inquiryId)) {
                return workbenchVendorQuotation;
            }
            AcquireInquiry acquireInquiry = rest.documentGet(BiWarehouseInquiryName, inquiryId.toString(), AcquireInquiry.class);
            if (Objects.isNull(acquireInquiry)) {
                log.warn("[BiEngineAnalysis-InternalVendorQuotationJob] 询价单"+inquiryId+"未找到!");
                return workbenchVendorQuotation;
            }
            workbenchVendorQuotation
                    .setInquiryDeadLine(acquireInquiry.getExpire())
                    .setInquiryTime(acquireInquiry.getTime())
                    .setCustomerName(StringUtils.trim(acquireInquiry.getOrganizationCompanyName()));
            return workbenchVendorQuotation;
        };
    }

    /**
     * 获取产品经理信息
     * @return
     */
    @Bean(ProcessName4)
    public ItemProcessor<WorkbenchVendorQuotation, WorkbenchVendorQuotation> processor4() {
        return workbenchVendorQuotation -> {
            BindingInventedVendorQuery query = BindingInventedVendorQuery.builder().vendorId(workbenchVendorQuotation.getVendorId()).build();
            BoolQueryBuilder boolQuery = BoolQueryBuilders.bindingInventedVendor(query);
            long count = rest.searchCount(BiWarehouseBindingInventedVendorName, boolQuery);
            ElasticSearchRest.SearchResultClz<AcquireBindingInventedVendor> result = rest.searchAsClz(BiWarehouseBindingInventedVendorName,
                    ElasticSearchRest.Page.of(1, ((int) (count))), boolQuery, null, null, AcquireBindingInventedVendor.class);
            if (Objects.nonNull(result) && !CollectionUtils.isEmpty(result.getData())) {
                List<String> ids = result.getData().stream().map(e -> Objects.toString(e.getBinderId())).collect(Collectors.toList());
                Map<String, AcquireMember> map = rest.documentMultiGet(BiWarehouseMemberName, ids, AcquireMember.class);
                if (!CollectionUtils.isEmpty(map)) {
                    String manager = map.values().stream().map(AcquireMember::getFullname).collect(Collectors.joining(","));
                    workbenchVendorQuotation.setManager(manager);
                } else {
                    workbenchVendorQuotation.setManager("");
                }
            } else {
                workbenchVendorQuotation.setManager("");
            }
            return workbenchVendorQuotation;
        };
    }

    @Bean(ProcessName5)
    public ItemProcessor<WorkbenchVendorQuotation, WorkbenchVendorQuotation> processor5() {
        return workbenchVendorQuotation -> {
            String id = EncryptUtils.MD5_32(workbenchVendorQuotation.getTime().toString() + VendorType.INTERNAL.name() + workbenchVendorQuotation.getQuotationGoodsId());
            workbenchVendorQuotation.setId(id);
            workbenchVendorQuotation.setType(VendorType.INTERNAL);
            return workbenchVendorQuotation;
        };
    }

    @Bean(WriterName2)
    public ElasticSearchClzItemWriter<WorkbenchVendorQuotation> writer2() {

        return new ElasticSearchClzItemWriter<>(client, mapper, BiDataMartName);
    }


    @Bean(JobName)
    public Job job(@Qualifier(StepName1) Step step1, @Qualifier(StepName2) Step step2,
                   @Qualifier(StepName3) Step step3,@Qualifier(StepName4) Step step4,
                   @Qualifier(StepName5) Step step5) {

        return jobBuilderFactory.get(JobName)
                .incrementer(new RunIdIncrementer())
                .start(step1)
                .next(step2)
                .next(step3)
                .next(step4)
                .next(step5)
                .listener(jobExecutionListener)
                .build();
    }

    @Bean(StepName1)
    public Step step1() {

        return stepBuilderFactory.get(StepName1)
                .<AcquireQuotationGoods, WorkbenchVendorQuotation>chunk(MAX_BATCH_NUM)
                .reader(reader1())
                .processor(processor1())
                .writer(writer1())
                .build();
    }

    @Bean(StepName2)
    public Step step2() {

        return stepBuilderFactory.get(StepName2)
                .<WorkbenchVendorQuotation, WorkbenchVendorQuotation>chunk(MAX_BATCH_NUM)
                .reader(reader2())
                .processor(processor2())
                .writer(writer1())
                .build();
    }

    @Bean(StepName3)
    public Step step3() {

        return stepBuilderFactory.get(StepName3)
                .<WorkbenchVendorQuotation, WorkbenchVendorQuotation>chunk(MAX_BATCH_NUM)
                .reader(reader2())
                .processor(processor3())
                .writer(writer1())
                .build();
    }

    @Bean(StepName4)
    public Step step4() {

        return stepBuilderFactory.get(StepName4)
                .<WorkbenchVendorQuotation, WorkbenchVendorQuotation>chunk(MAX_BATCH_NUM)
                .reader(reader2())
                .processor(processor4())
                .writer(writer1())
                .build();
    }

    @Bean(StepName5)
    public Step step5() {

        return stepBuilderFactory.get(StepName5)
                .<WorkbenchVendorQuotation, WorkbenchVendorQuotation>chunk(MAX_BATCH_NUM)
                .reader(reader2())
                .processor(processor5())
                .writer(writer2())
                .build();
    }


}
